/*
 * Copyright Aristool AG Switzerland
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */

package org.opprua.dss.application;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
import java.util.TreeSet;

import org.json.JSONObject;
import org.opprua.dss.adapter.OppruaSpringServlet;
import org.opprua.dss.data.Digest;
import org.opprua.dss.data.Result;
import org.opprua.tidy.beans.BeanHandler;

/**
 * TODO: add Java doc
 * 
 * Prepared for first commit: 23.02.2007
 * 
 * @author Lin Li
 */
public class ServerRunner {

  private DApplication application = null;

  public ServerRunner() {
  }

  private DApplication getApp(RBase base) {
    if (application == null) {
      application = RStarter.getApplication(base.getApplicationDefinitions());
    }
    return application;
  }

  public void run() {

    RBase base = OppruaSpringServlet.getLead();
    Map<String, String> errors = null;

    Digest d = digest(base);
    if (d == null) {
      errors = quickMap("Requery error",
                  "Failed to parse the incomming JSON object");
    } else {
      try {
        errors = run(base, d);
      } catch (Throwable e) {
        errors = quickMap("Application Exception", e.toString());
        e.printStackTrace();
      }
    }

    if (errors == null) {
      return;
    }

    StringBuffer buf = new StringBuffer("{");
    for (Iterator<String> iter = errors.keySet().iterator(); iter.hasNext();) {
      String k = iter.next();
      String v = errors.get(k);

      buf.append("\"").append(k).append("\": ").append(JSONObject.quote(v))
                  .append(", ");
    }
    buf.delete(buf.length() - 2, buf.length());
    buf.append("}");

    respond(base, buf);
  }

  private Digest digest(RBase base) {
    String data = base.getRequest().getParameter("data");
    try {
      JSONObject o = new JSONObject(data);
      List<String> cumu = new ArrayList<String>();
      JSONObject a = o.optJSONObject("points");
      if (a != null) {
        for (Iterator<?> i = a.keys(); i.hasNext();) {
          cumu.add((String) i.next());
        }
      }
      return new Digest(o.getString("entry"), o.optString("component"), o
                  .optJSONObject("data"), cumu.toArray(new String[cumu.size()]));
    } catch (Throwable e) {
      e.printStackTrace();
      return null;
    }
  }

  private Map<String, String> run(RBase base, Digest d) {
    DApplication app = getApp(base);

    String[] guards = app.getGuards().toArray(new String[0]);
    Stack<String> entries = new Stack<String>();
    for (int i = 0; i < guards.length; i++) {
      Map<String, String> res = app.getEntry(guards[i]).execute(null, app,
                  entries);
      if (res != null) {
        res.put("ExceptionType", "validation");
        return res;
      }
    }

    if (!d.getEntryIsEmpty()) {
      Class<?> e = app.getEntry(d.getEntry()).getType();
      Object p = null;
      if (e != null) {
        try {
          p = BeanHandler.fromJSON(e, d.getData());
        } catch (Throwable t) {
          t.printStackTrace();
          return quickMap("Requery error",
                      "Failed to match input data object with the entry data type "
                                  + d.getEntry());

        }
      }
      Map<String, String> res = app.getEntry(d.getEntry()).execute(p, app,
                  entries);
      if (res != null) {
        res.put("ExceptionType", "validation");
        return res;
      }
    }

    StringBuffer buf = new StringBuffer();
    Set<String> cumu = new TreeSet<String>();
    cumu.addAll(Arrays.asList(d.getDataPoints()));
    if (!d.getComponentsIsEmpty()) {
      List<String> pts = runComponent(app, d.getComponents(), buf);
      cumu.addAll(pts);
    }

    boolean has = false;
    for (Iterator<String> iter = cumu.iterator(); iter.hasNext();) {
      String name = iter.next();
      DDataPoint dp = app.getDataPoint(name);
      if (d == null) {
        return quickMap("Input error", "No such data point: " + name);
      }
      Result<?> res = null;
      try {
        res = dp.execute();
      } catch (Exception e) {
        return quickMap("application exception", "failure at data point "
                    + name);
      }
      if (res.getType() == Result.TYPE.FAILURE) {
        return res.getReason();
      }
      Object dd = res.getData();
      if (dd == null) {
        return quickMap("ApplicationError", "Data point failed to deliver: "
                    + name);
      }
      if (!has) {
        if (buf.length() > 0) {
          buf.append(",\n");
        }
        buf.append("data: {");
        has = true;
      }
      buf.append("").append(name).append(": ").append(BeanHandler.toJSON(dd))
                  .append(",\n");
    }
    if (has) {
      buf.delete(buf.length() - 2, buf.length());
      buf.append("}");
    }
    buf.insert(0, "{");
    buf.append("}");

    respond(base, buf);
    return null;
  }

  private void respond(RBase base, StringBuffer buf) {
    String s = buf.toString();
    try {
      base.getResponse().setContentType("text/json");
      PrintWriter p = base.getResponse().getWriter();
      p.append(s);
      p.flush();
    } catch (Throwable e) {
      e.printStackTrace();
    }
  }

  private Map<String, String> quickMap(String key, String value) {
    Map<String, String> m = new TreeMap<String, String>();
    m.put(key, value);
    m.put("ExceptionType", "application");
    return m;
  }

  private List<String> runComponent(DApplication app, String name,
              StringBuffer buf) {
    DComponent c = app.getComponent(name);
    if (c == null) {
      String n = runSelect(app, name);
      if (name == null) {
        throw new Error("No such component found: " + name);
      }
      if (name.length() == 0) {
        return new ArrayList<String>();
      }
      name = n;
      c = app.getComponent(name);
    }
    if (c == null) {
      throw new Error("No such component found: " + name);
    }
    List<String> d = new ArrayList<String>();
    buf.append("component: ").append(c.getDescriptor(app));
    DData[] da = c.getData();
    for (int i = 0; i < da.length; i++) {
      d.add(da[i].getName());
    }
    return d;
  }

  private String runSelect(DApplication app, String name) {
    DSelect s = app.getSelect(name);
    if (s == null) return null;
    DSelector[] l = s.getSelectors();
    for (int i = 0; i < l.length; i++) {
      boolean got = true;
      if (l[i].getCall() != null) {
        got = l[i].getCall().call(null).getType() == Result.TYPE.SUCCESS;
      }
      if (got) {
        return l[i].getComponent();
      }
    }
    return "";
  }
}
